其他

Hystrix-开源容错系统

2018-02-24 龚海余 Java面试那些事儿

点击上方“Java面试那些事儿”,选择“置顶公众号”

技术文章第一时间送达!


开年第一篇文章来自于京东的小伙伴,希望这篇文章能够帮助大家对熔断和降级有所理解。


1. 概述


因印尼双十二促销,系统中存在个别服务问题,组内领导考虑到在目前的网关中加入限流。虽然在Nginx层加入了引流。但在促销时,个别服务问题会导致服务器级联吃空CPU和内存,导致服务超时响应。加入hystrix,虽然消耗掉一些性能,但是能防止系统雪崩,能提高系统整体服务能力。下面从hystrix的原理和网关如何引入hystrix两个方面进行讲解。


2. 一般系统存在的问题


无论是采用SOA架构的系统,还是微服务架构系统。都会存在一个用户请求在后台由多个服务在支撑,如下图1:一个用户请求由Tomcat的某个线程接收,该线程请求网络上的依赖服务A,H,I和P。当I出现拒绝服务时,就会导致该用户请求一直占用Tomcat的线程,直到Tomcat的线程超时(超时时间由connectionTimeout="20000"设置,默认为20s,maxThreads:最大并发连接数,默认为200,maxSpareThreads:这个参数标识,一旦创建的线程数量超过这个值,Tomcat就会关闭不活动的线程,默认为50)。


图1 一般系统内部依赖关系


一般系统从整个框架来看,如下图的API网关, Web、Android等设备都从API获得数据。

图2 来自腾讯API网关


API网关概念:API 网关(API Gateway)是 API 托管服务。提供 API 的完整生命周期管理,包括创建、维护、发布、运行、下线等。您可使用 API Gateway 封装自身业务,将您的数据、业务逻辑或功能安全可靠的开放出来,用以实现自身系统集成、以及与合作伙伴的业务连接。


从API网关内部来看,如图1。容器用某个线程接收到用户请求(Tomcat为例),容器线程从远程获取各个依赖的服务资源,组装计算好并返回。如果单个网络调用在每秒50次请求以上,用户请求将因延迟而阻塞,所有的请求线程也将在短时内阻塞。这种结构有以下问题:


  1. 单个rpc延迟、失败重试由jsf控制。但没有统计延迟失败信息,也无法预测失败。

  2. 单个依赖阻塞,当不能快速熔断时,线程一直占用cpu及内存资源导致级联影响,甚至引起雪崩。因为无法快速失败。

  3. 监控由ump控制,ump是公司内部统一监控平台,其他公司不一定有监控。


考虑到应用容器的线程数目基本都是固定的(比如tomcat的线程池默认200),当在高并发的情况下,某一外部依赖服务超时阻塞,就有可能使得整个主线程池被占满,这是长请求拥塞反模式。如在秒级打到该容器上存在多个请求,大于Tomcat应用容器的线程数目,且其中某个服务提供者阻塞的情况下,将导致所有请求所在的线程阻塞。如下图所示。全部线程栈占用内存和CPU将导致整个机器拒绝服务,而依赖该服务的其他服务,就又可能会重复产生上述问题。因此整个系统就像雪崩一样逐渐的扩散、坍塌。


3. 什么是hystrix?


hystrix([hɪst'rɪks])是豪猪的意思。豪猪是一种全身是刺的动物,netflix使用hystrix意味着hystrix能够像豪猪的刺一样保护你的应用。Hystrix是Netflix(网飞公司)开源的一款容错系统。该框架为分布式环境提供了弹性、增加了延迟容忍和容错。



3.1 雪崩产生原因?


  1. 服务提供者不可用,导致服务调用者线程资源耗尽是产生雪崩的原因之一。

  2. 服务调用者自身流量激增,导致系统负载升高。比如异常流量、用户重试、代码逻辑重复。

  3. 缓存到期刷新,使得请求都流向数据库,Cache瞬间命中率为0,相当于Cache被击穿。

  4. 重试机制,比如我们rpc框架的retry次数,每次重试都可能会进一步恶化服务提供者。

  5. 硬件故障,比如机房断点,电缆被挖等等。


3.2 雪崩实例


主要模拟服务提供者不可用。在Controller层通过当前线程做循环操作来模拟服务提供者的耗时操作导致的服务调用者线程占用大量内存。代码如下:



通过ab命令,一共发起10万个请求,同时并发为100,并激活每个请求的keepAlive属性。(-k:激活HTTP中的“keepAlive”特性)。用jvisualvm来监控java线程的活动状态,如下图左边内容。


通过左图cpu使用率可以看到,一旦开启测试,cpu立即上升,并维持在80%左右。GC活动一直占用很低的cpu利用率。第二图堆大小维持在1000MB左右,而被使用的堆因GC回收成锯齿状上下波动。并且波峰都维持在80%。左下角图中,总的类加载维持在一个相对稳定的状态(6000以上),后台线程与可用线程维持在150以下的一个稳定值。



整个系统资源几乎都为Tomcat容器耗尽。如下图。4核将近有3核在运行Tomcat。8G内存也几乎将全部耗尽。


当kill掉ab测试。资源将成下图变化。cpu和内存使用率急剧下降。请读者分析左四图形成的原因。



通过上面的测试,如果单个网络请求如果被阻塞,在短时间内的系统开销将变得不容乐观。


3.3 常见的解决方案


针对上述雪崩情景,有很多应对方案,但是没有一个万能的模式能够应对所有的情况。


  1. 针对服务调用者自身流量激增,我们可以采用auto-scaling方式进行自动扩容以应对突发流量,或在负载均衡上安装限流模块。参考:春节日活跃用户超一亿,探秘如何实现服务器分钟级扩容。

  2. 针对缓存到期刷新,我们也有很多方案,参考Cache应用中的服务过载案例研究。

  3. 针对重试机制,我们可以减少或关闭重试,直接采用failfast,或采用failsafe进行优雅降级。

  4. 针对硬件故障,我们可以做多机房容灾,异地多活等。

  5. 针对服务提供者不可用,可以使用资源隔离,熔断机制等避免在分布式系统中,单个组件的失败导致的级联影响,甚至是雪崩的情况。参考Martin Fowler的熔断器模式。


而Hystrix采用缓存机制减少对后台服务的请求,缓存刷新由自己代码控制。采用熔断器回退fallback机制能够解决针对重试的快速失败。如果存在异地多活,Hystrix可以配置成主备双活机制。针对服务不可用,Hystrix作用在客户端,客户端程序依赖Hystrix相关的第三方包,使得客户端与所依赖的服务形成资源隔离,通过线程隔离或者信号量隔离保护主线程池(Tomcat线程池,调用者线程池),使用熔断器的快速失败,迅速恢复机制,当单个组件服务失败率到一定程度后,再次请求,会直接响应失败,并且之后会有重试机制。另外,Hystrix提供优雅降级及近乎实时的监控。


3.4 hystrix设计原则


  1. 防止任何单个依赖占满Web容器线程池。

  2. 直接响应失败,而不是一直等待。-不放到队列排队。

  3. 提供错误返回接口,而不是让用户线程直接处理依赖服务抛出的异常。

  4. 使用隔离或熔断技术来降低并限制单个依赖对整个系统造成的影响(耗尽系统资源)。内部通过舱壁隔离(线程隔离和信号量隔离)使请求互不影响,熔断器机制使本次请求Fast Fail。


3.5 hystrix如何解决以上问题?


  1. 对外部系统的请求,使用HystrixCommand包装,并使用单独的线程执行。

  2. 可对服务请求设置timeout,超时后,直接返回,可根据平时监控,查看该服务95%的请求的运行时间为多少进行设置。

  3. 维护一个小的线程池在客户端,专门处里对指定服务的请求,当线程使用量超过线程池容量时,直接返回响应,而不是排队等待处里。

  4. 触发熔断来停止所有对指定服务在一定时间范围内的请求,针对该服务请求的错误数百分比可手动,可自动熔断。

  5. 对超时,失败,或者熔断,做特殊错误返回处理fallback。


4. Hystrix工作原理


4.1 加入Hystrix后系统的依赖关系


Hystrix采用舱壁隔离的原则,将外部依赖的每个请求包装成一个个小线程池。每个线程池负责一个请求资源的调用。当某个线程池出现资源拒绝获取或者超时时,将只会导致该线程池被打满,当再次有其他主线程池中线程请求资源时,Hystrix会快速失败,而不会影响整个应用主容器资源被耗尽,如下图。

4.2 Hystrix工作流


  1. 构建HystrixCommand或者HystrixObservableCommand对象;

  2. 执行命令execute()、queue()、observe()、toObservable();

  3. 如果请求结果缓存这个特性被启用,并且缓存命中,则缓存的回应会立即通过一个Observable对象的形式返回;

  4. 检查熔断器状态,确定请求线路是否是开路,如果请求线路是开路,Hystrix将不会执行这个命令,而是直接使用『失败回退逻辑』,即不会执行run(),直接执行getFallback();

  5. 如果和当前需要执行的命令相关联的线程池和请求队列(或者信号量,如果不使用线程池)满了,Hystrix 将不会执行这个命令,而是直接使用『失败回退逻辑』,即不会执行run(),直接跳转到8执行getFallback();

  6. 执行HystrixCommand.run()或HystrixObservableCommand.construct(),如果这两个方法执行超时或者执行失败,则执行getFallback();如果正常结束,Hystrix 在添加一些日志和监控数据采集之后,直接返回回应;

  7. Hystrix会将请求成功,失败,被拒绝或超时信息报告给熔断器,熔断器维护一些用于统计数据用的计数器。

  8. 得到 Fallback。

  9. 返回成功的响应。


4.3  设计模式


4.3.1 命令模式


命令模式类似于司令员发出命令让士兵执行。其中有三个实体,一个是司令员,一个是士兵,一个是命令。如下图,主线程相当于于司令员,发出一个YouCommand命令,而该命令继承HystrixCommand<R>,HystrixCommand<R>继承AbstractCommand<R>,这两者都实现HystrixExecutable接口。最后由依赖线程(士兵)执行。所以,Tomcat线程中某个类中必须有YouCommand对象,最后由依赖线程池执行。



4.3.2 观察者模式


观察者模式是广播机制的核心。此模式中有两个实体:观察者及观察的目标对象。把多个订阅者、客户称为观察者。需要同步给多个订阅者的数据封装到对象中,称为观察者的目标(订阅主题)。如果站在主题的角度考虑:一个主题,往往有多个观察者在观察。Hystrix通过观察者模式对服务进行状态监听。 


每个任务都包含一个对应的Metrics,所有Metrics都由一个ConcurrentHashMap来进行维护,Key是CommandKey.name(),在任务的不同阶段会往Metrics中写入不同的信息,Metrics会对统计到的历史信息进行统计汇总,供熔断器使用。如下图,Metrics Storage中保存了每个接口的访问结果数据。


4.3.3 Hystrix命令模式和观察者模式的应用


  1. 同步执行。调用.execute()方法。将会阻塞当前线程直到获取结果。

  2. 异步执行。调用.queue()方法。不阻塞当前线程,返回一个Future对象。从图上可以看出,.queue().get()等于同步调用.execute()。

  3. 热注册观察者执行。调用.observer()方法。返回一个Observable对象,当run方法执行完后,进入观察者订阅的事件中。

  4. 冷注册观察者执行。调用.toObservable()方法。同样返回一个Observable对象。但在注册时即执行run()方法。


无论你使用哪种方式引发命令,Hystrix Command总是回到Observable对象的形式。


4.4 断路器


下图展示了HystrixCommand或HystrixObservableCommand怎样与HystrixCircuitBreaker交互及它的逻辑和决策流程图,包括在熔断器中如何计数等。


下面描述了断路器打开或者关闭发生的精确方式。


  1. 假设大量的请求数量超过了HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()的阈值【1】,并且依赖调用失败的百分比超过了HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()的阈值【2】,熔断器将会从关闭状态变成打开状态;

  2. 在熔断器处于打开状态的期间,所有对这个依赖进行的调用都会短路,即不进行真正的依赖调用,返回失败;

  3. 在等待(冷却)的时间超过HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()的值后,熔断器将处于半开的状态,将允许单个请求去调用依赖,如果这次的依赖调用还是失败,熔断器状态将再次变成打开,这个打开状态持续时间是HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()配置的值;如果这次的依赖调用成功,熔断器状态将变成关闭,后续依赖调用可正常执行。


下面描述了熔断器中的计数方式。其中涉及到circuitBreakerSleepWindowInMilliseconds、circuitBreakerRequestVolumeThreshold、circuitBreakerErrorThresholdPercentage三个值的理解。


先说下Metrics如何统计?


Metrics在统计各种状态时,采用滑动窗口的思想。在一个滑动窗口时间中又划分为多个Bucket(这里滑动窗口时间与Bucket一定成倍数关系,否则会报错),滑动窗口的移动以Bucket为单位滑动,而每个HealthCounts对象记录一个Buckets的监控状态,Buckets为一个滑动窗口的一小部分,如果一个滑动窗口时间为t,Bucket数量为n,那么每t/n秒将新建一个HealthCounts对象。



三个值的意义?


circuitBreakerSleepWindowInMilliseconds(断路器睡眠窗口时间):此属性用来设置电路跳闸之后拒绝请求之前允许再次尝试的时间大小用来决定电路是否应该再次关闭。


circuitBreakerRequestVolumeThreshold(断路器请求容量阈值):这个属性设置滚动窗口中的最小请求数量来决定是否触发断路器。比如,如果这个值为20,然后如果仅仅只有19个请求在滚动窗口(指的是10秒钟的窗口)能接收到,即时这19个请求都失败,电路也不会触发断路器打开。


circuitBreakerErrorThresholdPercentage(断路器错误阈值百分比):这个属性设置错误百分比之上的电路应该会被触发打开开始短路,请求转到fallback逻辑。


4.5 降级回退方式


降级的常用处理方式,返回默认值,返回缓存里的值(包括本地缓存,远程redis缓存),但是回退的处理方式有不适合的场景。如写操作、批处理、计算等等。以上几种情况如果失败,则程序就要将错误返回给调用者。


Fail Fast快速失败


如果我们实现的是HystrixObservableCommand.java则 重写 resumeWithFallback方法。



Fail Silent 无声失败


返回null,空Map,空List。如图。重写getFallback()方法。

Fallback: Static 返回默认值


回退的时候返回静态嵌入代码中的默认值,这样就不会导致功能以Fail Silent的方式被清楚,也就是用户看不到任何功能了。而是按照一个默认的方式显示。


Fallback: Stubbed 自己组装一个值返回


在Fallback方法中组装初始化好的值。


Fallback: Cache via Network 利用远程缓存


通过远程缓存的方式,在失败的情况下再次发起一次remote请求缓存数据。由于是又发起一起远程调用,所以会重新封装一次Command,这个时候要注意,执行fallback的线程一定要跟主线程区分开,也就是重新命名一个ThreadPoolKey。


Primary + Secondary with Fallback 主次方式回退(主要和次要)


这种方式类似于我们上线一个新的功能,但为了防止新功能上线失败可以回退到老的代码,我们会做一个开关比如使用zookeeper做一个配置开关,可以动态切换到老代码功能。Hystrix使用通过一个配置来在两个command中进行切换。

代码如下:usePrimary可以通过ucc来配置。


4.6 隔离策略


执行依赖代码的线程与请求线程(比如Tomcat线程)分离,请求线程可以自由控制离开的时间,这也是我们通常说的异步编程,Hystrix是结合RxJava来实现的异步编程。通过设置线程池大小来控制并发访问量,当线程饱和的时候可以拒绝服务,防止依赖问题扩散。


4.6.1 线程隔离


优缺点


  1. 应用程序会被完全保护起来,即时依赖的一个服务线程池满了,也不会影响到应用程序的其他部分。

  2. 当依赖的服务恢复时,应用程序能恢复到正常的性能。

  3. 当参数配置错误时,线程池的状态会很快显示出来,比如延迟、超时、拒绝等。同时可以通过动态属性实时执行来处理纠正错误的参数配置。

  4. 如果服务的性能有变化,从而需要调整,比如增加或者减少超时时间,更改重试次数,就可以通过线程池指标动态属性修改,并不会影响到其他调用请求。注意:尽管线程池提供了隔离,但是我们的客户端底层代码也必须得有超时设置,不能无限制的阻塞以致线程池一直饱和。

  5. 线程池的主要缺点就是它增加了计算的开销,每个业务请求(被包装成命令)在执行的时候,会涉及到请求排队,调度和上下文切换。不过Netflix公司内部认为线程隔离开销足够小,不会产生重大的成本或性能的影响。



性能花费


Netflix API每天使用线程隔离的方式处理10亿多的Hystrix Command任务,每个API实例都有40多个线程池,每个线程池都有5-20个线程(大多数设置为10)

下图显示了一个HystrixCommand在单个API实例上每秒执行60个请求(每个服务器每秒执行大约350个线程执行总数)。从图上可知:


在中间偏下位置,用单一线程是没有性能消耗的。


TP90的消耗大概是单一线程为3ms。


单一线程TP99大概是有9ms的消耗。但是增长的性能花费远小于单一线程增长的执行时间(网络请求),线程执行时间从2-28ms然而花费从0-9ms。


对大多数Netflix使用的案例,这样TP90或者更高的电路响应时间(花销)是可以接受的,这样换取了系统的韧性。



4.6.2 信号量隔离


对于不依赖网络访问的服务,比如只依赖内存缓存这种情况下,就不适合用线程池隔离技术,而是采用信号量隔离。


信号量隔离只是限制了总的并发数,服务使用主线程进行同步调用,即没有线程池。因此,某个服务的如果只是想限制总并发调用量或者调用的服务不涉及远程调用的话,可以使用轻量级的信号量来实现。



4.6.3 隔离策略对比


这张图说明了线程池隔离和信号量隔离的主要区别:线程池方式下业务请求线程和执行依赖的服务的线程不是同一个线程;信号量方式下业务请求线程和执行依赖服务的线程是同一个线程。

下表对比了线程池和信号量主要的特点异同。信号量和线程池隔离方式都支持熔断和限流。

信号量隔离图解。

线程池隔离图解。


4.7 Collapsing


使用场景:HystrixCollapser用于对多个相同业务的请求合并到一个线程甚至可以合并到一个连接中执行,降低线程交互次和IO数,但必须保证他们属于同一依赖。


  • 产生原因?无论是SOA架构还是微服务架构的系统,往往会通过RPC方式或者HTTP请求方式依赖外部的接口。而远程调用过程中最常见的问题就是通信消耗与连接数占用。在高并发情况下,因通信次数的增加,调用方在本地线程池缓存的请求数、被调用方的性能消耗和网络通信次数都会导致总的通信时间变得不那么理想。为了优化这些问题,Hystrix提供了HystrixCollapser来实现请求的合并,以减少网络通信消耗和本地线程的占用。

  • HystrixCollapser实现了在HystrixCommand之前放置一个合并处理器,它会在一个很短的时间内(通过timerDelayInMilliseconds参数设置,默认是10毫秒)内对同一依赖服务的多个请求进行整理合并以批量方式发起请求(服务端需提供批量的接口)。通过HystrixCollaper的封装,开发者不需要去关注线程合并和请求结果分发的细节,只需关注批量化服务。如下图,没有Collapsing时,请求数=线程数=网络连接数。当使用Collapsing,将每个提交的请求合并到Collapser,通过1个线程完成数据的获取,所以,窗口中的请求数=1个线程=1个网络连接。

  • 请求合并的使用场景

    在选择是否使用Collapers时,主要考虑下面两个方面。

    (1)请求命令本身的延迟。如果依赖服务的请求命令本身是一个高延迟的命令,则可以使用请求合并器,因为延迟时间窗的时间消耗就显得微不足道了。

    (2)延迟时间窗内的并发量。如果时间窗内只有很小的并发,这样反而会导致系统瓶颈,因为每个请求需要多消耗一个时间窗才响应。如果一个时间窗内有很高的并发,并且服务方也实现了批量处理接口,那么Collapser可以有效减小网络连接数和提升系统吞吐量。此时延迟时间窗所增加的消耗就忽略不计了。


对于Collapser,个人建议慎用。除非你能准确评估两者的利弊。


5. 实例


5.1 Hello World



5.2 SpringMVC-注解方式



步骤1:获取切入点方法;

步骤2:根据方法的注解HystrixCommand或者HystrixCollapser生成相应的CommandMetaHolderFactory或者CollapserMetaHolderFactory类。

步骤3:将原方法的属性set进metaHolder中;

步骤4:根据metaHolder生成相应的HystrixCommand,包含加载hystrix配置信息。commandProperties加载的优先级为前缀hystrix.command.commandKey > hystrix.command.default > defaultValue(原代码默认);threadPool配置加载的优先级为 前缀hystrix.threadpool.groupKey.> hystrix.threadpool.default.> defaultValue(原代码默认)。

步骤5:执行命令。


倘若需要给该方法指定groupKey和commandKey定义其fallback方法,则可通过添加注解属性来实现。


5.2.1 Command Properties


Execution


包括

execution.isolation.strategy、

execution.isolation.thread.timeoutInMilliseconds、

execution.timeout.enabled、

execution.isolation.thread.interruptOnTimeout、

execution.isolation.thread.interruptOnCancel、

execution.isolation.semaphore.maxConcurrentRequests。


Fallback


包括

fallback.isolation.semaphore.maxConcurrentRequests、

fallback.enabled。


Circuit Breaker


包括

circuitBreaker.enabled、

circuitBreaker.requestVolumeThreshold、

circuitBreaker.sleepWindowInMilliseconds、

circuitBreaker.errorThresholdPercentage、

circuitBreaker.forceOpen、

circuitBreaker.forceClosed。


Metrics


包括

metrics.rollingStats.timeInMilliseconds、

metrics.rollingStats.numBuckets、

metrics.rollingPercentile.enabled、

metrics.rollingPercentile.timeInMilliseconds、

metrics.rollingPercentile.numBuckets、

metrics.rollingPercentile.bucketSize、

metrics.healthSnapshot.intervalInMilliseconds。


Request Context


包括

requestCache.enabled、

requestLog.enabled。


5.2.2 Collapser Properties


包括

maxRequestsInBatch、

timerDelayInMilliseconds、

requestCache.enabled。


5.2.3 Thread Pool Properties


包括coreSize、maximumSize、maxQueueSize、queueSizeRejectionThreshold、keepAliveTimeMinutes、allowMaximumSizeToDivergeFromCoreSize、metrics.rollingStats.timeInMilliseconds、metrics.rollingStats.numBuckets。


5.3 功能总结


5.3.1 Core function


  • Isolate


Maintain a small thread pool for each dependency.  

                  -- avoid cascading failures into whole system.

                  -- rapidly recover 

                 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool"))


  • Latent control 


NOT available when choose SEMAPHORE isolation

                 .withExecutionTimeoutInMilliseconds(3000)))


  • Fail fast


a. If pool is full, be immediately rejected instead of queued up;

                 b. Circuit breaker (https://github.com/Netflix/Hystrix/wiki/How-it-Works#circuit-breaker)


Fallback and degrade gracefully implements getFallback() method


5.3.2 Extension function


  • Request Collapsing

  • Request Caching

  • Near real-time monitoring, alerting, and operational control


6. 网关引入和实现思路


6.1 接入姿势


姿势很重要!从Controller引入还是从RPC层引入?如果从Controller引入,Controller对应的方法由一个线程去通过RPC方式获取所有的资源。如果是从RPC层引入,则每个RPC方法对应一个线程池,这样可以灵活控制每个RPC接口的访问情况。网关接口使用这种方式引入。


6.2 实现思路


参数设置依据


Thread Size = peak healthy × 99th percentile latency in seconds + some breathing room(线程池大小 = 峰值QPS * 99耗时 + 预留空间 )


超时设定标准


With retry,Time = 99th mean-time + 50th mean-time

 Without Retry,Time = 99.5meantime


7. 总结


本文主要从hystrix原理和实现两个方面对hystrix进行讲解。其中对原理的实现上可能会有没有考虑到的问题,因公司内部已有监控的服务,对hystrix监控没有进行讲解,其他有疑问之处,欢迎拍砖。


8. 参考


降级回退方式 

https://www.jianshu.com/p/b6e8d91b2a96?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

各种注解属性http://zyouwei.com/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/Java/Hystrix-configuration.html

请求合并

https://www.jianshu.com/p/ddaa71462b26

hystrix-翻译比较好的文章

http://youdang.github.io/2016/02/05/translate-hystrix-wiki-how-it-works/

http://www.cnblogs.com/java-synchronized/p/7927726.html

https://www.jianshu.com/p/73a07e06a9d5?from=timeline

https://www.jianshu.com/p/6c574abe50c1?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

http://blog.csdn.net/qq_17751605/article/details/51225976

http://blog.csdn.net/xiaojia1100/article/details/65631778


推荐阅读:

技术:你应该学习一下Openresty(许多大厂都在用)了!!!

安全:一起来学习用JNI加固你的Java代码,文末有彩蛋哦

分享:【面试题系列】为什么面试总是失败收场?

记得转发给更多的小伙伴哦~

一个坚持原创的技术型公众号。站在初学者的角度来深挖每一个知识点,站在面试官的角度来分析每一道面试题,站在攻击者的角度来写好每一篇防御案例,已帮助不少小伙伴进入了一线互联网企业。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存